今天我們來認識一下兩個重要的 AsyncPipe 特性,可以幫助我們在使用 AsyncPipe 時更有信心,打造出更高效能的程式!
類型:觀念/技巧
難度:5 顆星
實用度:4 顆星
先來看看這段簡單的程式碼:
import { Component, OnInit, OnDestroy} from '@angular/core';
import { interval } from 'rxjs';
@Component({
selector: 'app-counter',
template: `{{ value }}`
})
export class CounterComponent implements OnInit, OnDestroy {
value = 0
ngOnInit() {
interval(1000).subscribe((counter) => {
console.log(counter);
this.value = counter;
})
}
ngOnDestroy() {
console.log('destroy');
}
}
@Component({
selector: 'my-app',
template: `
<app-counter *ngIf="display"></app-counter>
<button (click)="display = !display">Toggle</button>
`,
})
export class AppComponent {
display = true;
}
在上面的程式碼中,我們設計了 CounterComponent
並使用 RxJS 的 interval()
在訂閱後每秒變更一次資料,另外在畫面上設計一個按鈕來決定是否需要銷毀這個元件,當 display
為 false
時,<app-counter>
元件將會被銷毀,而當 display
為 true
時, <app-counter>
元件將重新產生。
看起來一切沒什麼問題,但是當我們打開 F12 時會發現,雖然元件被摧毀了,但 interval()
的行為並沒有停止!這將會造成每次產生元件時,就會產生一段新的 interval()
,當次數多了後將會佔據大量的記憶體,進而發生 memory leak 的問題;要避免這問題,最直覺的方式是當元件要被璀毀時,於 ngOnDestroy
方法內使用 unsubscribe
取消訂閱:
export class CounterComponent implements OnInit, OnDestroy {
value = 0
subscription: Subscription;
ngOnInit() {
this.subscription = interval(1000).subscribe((counter) => {
console.log(counter);
this.value = counter;
})
}
ngOnDestroy() {
console.log('destroy');
this.subscription.unsubscribe();
}
}
雖然取消訂閱人人有責,但是當程式中的 observable 越來越多時,總是會有不小心忘記訂閱的時候,這時 AsyncPipe 就能派上大作用啦!
從 AsyncPipe 的程式碼可以看到,當 AsyncPipe 處理 observable 時,會在 ngOnDestoy 時自動將 observable 退訂!因此上面的程式我們可以簡單改寫為:
@Component({
selector: 'app-counter',
template: `{{ value$ | async }}`
})
export class CounterComponent implements OnInit {
value$: Observable<number>;
ngOnInit() {
this.value$ = interval(1000).pipe(
tap(counter => console.log(counter))
);
}
}
由於 AsyncPipe 會自動退訂的關係,我們不再需要手動執行退訂的程式,整個程式看起來是不是清爽多啦!
在 AsyncPipe 程式碼的另一角落,我們可以發現在資料變更時(不管是 Promise 還是 RxJS),會自動使用 ChangeDetectorRef
的 markForCheck()
方法,自動要求變更偵測發生;會有這樣的程式需求也不難理解,當我們給予一個 observable 實體時,不管內部的值再怎麼變化,observable 的實體參考位置也不會變化,因此當元件的變更偵測策略為 OnPush
時,使用 AsyncPipe 就會發生沒有進行變更偵測的問題!所以 AsyncPipe 在訂閱(或呼叫 then()
)的同時,也會要求變更偵測需要處理!
透過上述提到的特性,如果元件中只剩下 observable + AsyncPipe 時,我們就可以光明正大地把元件的 OnPush
策略打開,並且完全不用手動去呼叫 markForCheck
方法,AsyncPipe 會在需要變更偵測時主動幫我們處理!
@Component({
selector: 'app-counter',
template: `{{ (data$ | async)?.value }}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
@Input() data$: Observable<any>;
ngOnInit() { }
}
從此以後每個單純用來顯示資料的元件,再打開 OnPush
之後,既能夠維持元件一定程度的高效能,又不怕忘記呼叫 markForCheck
啦!
善用 RxJS 與 AsyncPipe,要打造出既好維護,相對效能又高的元件一點都不困難啊!!
今天的程式碼參考連結:
https://stackblitz.com/edit/ironman2019-asyncpipe-onpush?file=src/app/app.component.ts
不好意思,請問一下,所以如果在 component 裡面有寫到 subscribe() 的話,那麼在 ngDestory 時,一定要去做 unsubscribe()
這樣理解是對的嗎 ??
不完全正確,Angular 內建的服務如果有 observable 的話,多半會再元件 destroy 時幫你做 ubsubscribe
另外如果在元件 destroy 前就 complete 的話,也不會有 unsubscribe 的必要(如 HttpClient)
簡單來說,當你知道某個 observable 在 destroy 後不會結束的話,就需要手動處理 unsubscribe
如果真的無法判斷,全部手動 unsubscribe 也是無所謂的
原來如此! 我懂了^^ 感謝感恩